#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>

#include "minilzo.h"
#define BUFFER_SIZE           4096
#define COMPRESS_BUFFER_SIZE  BUFFER_SIZE
#define DCOMPRESS_BUFFER_SIZE BUFFER_SIZE
int compress(void *src, int src_size, void *dst, int *dst_size);
int decompress(void *src, int src_size, void *dst, int *dst_size);
unsigned int crc32_st(unsigned int *pbuf, unsigned int size);
int pack_lzo(char *path);
static void error_cmd(void)
{
  printf("command wrong.\n");
  printf("minilzo -c srcfile: compress srcfile\n");
  printf("minilzo -x srcfile: decompress srcfile\n");
}

int main(int argc, char **argv)
{
  int f_src, f_dst;
  char output_path[128];
  int mode;
  int src_size, dst_size;
  unsigned char src_buf[BUFFER_SIZE*2];
  unsigned char dst_buf[BUFFER_SIZE*2];
  unsigned char header[4];

  if(argc != 3) {
    error_cmd();
    return -1;
  }

  if(strlen(argv[1]) != 2) {
    error_cmd();
    return -1;
  }
  if(memcmp(argv[1], "-", 1)) {
    error_cmd();
    return -1;
  }
  else {
    if (argv[1][1] == 'c') {
      mode = 0;
    }
    else if (argv[1][1] == 'x') {
      mode = 1;
    }
    else {
      error_cmd();
      return -1;
    }
  }

  if (lzo_init() != LZO_E_OK)
  {
    printf("lzo_init faild.\n");
    return -1;
  }

  f_src = open(argv[2], O_RDWR);
  if(f_src == -1) {
    perror(argv[2]);
    return -1;
  }
  
  if(mode) {
    if(strlen(argv[2]) > 122) {
      close(f_src);
      printf("src file name length is too long.\n");
      return -1;
    }
    sprintf(output_path, "%s.dlzo", argv[2]);
  }
  else {
    if(strlen(argv[2]) > 122) {
      close(f_src);
      printf("src file name length is too long.\n");
      return -1;
    }
    sprintf(output_path, "%s.clzo", argv[2]);
  }
  remove(output_path);
  f_dst = open(output_path, O_RDWR|O_CREAT|O_SYNC, S_IRUSR|S_IWUSR);
  if(f_dst == -1)
  {
    close(f_src);
    perror(output_path);
    return -1;
  }
  lseek(f_src, 0, SEEK_SET);
  lseek(f_dst, 0, SEEK_SET);
  while(1)
  {
    if(mode) {
      if(read(f_src, header, 4) == 0) break;
      src_size = header[0] * (1 << 24) + header[1] * (1 << 16) + header[2] * (1 << 8) + header[3];
      memcpy(src_buf, header, 4);
      read(f_src, &src_buf[4], src_size);
      src_size += 4;
      decompress(src_buf, src_size, dst_buf, &dst_size);
      printf("src_size = %d dst_size = %d\n", src_size, dst_size);
      write(f_dst, dst_buf, dst_size);
    }
    else {
      src_size = read(f_src, src_buf, BUFFER_SIZE);
      if(src_size)
      {
        compress(src_buf, src_size, dst_buf, &dst_size);
        printf("src_size = %d dst_size = %d\n", src_size, dst_size);
        write(f_dst, dst_buf, dst_size);
      }
      else {
        break;
      }
    }
  }
  close(f_src);
  close(f_dst);
  if(mode == 0) pack_lzo(output_path);
  return 0;
}

int pack_lzo(char *path)
{
  int file_size;
  int f_src, f_dst;
  int src_size;
  unsigned char src_buf[BUFFER_SIZE];

  f_src = open(path, O_RDWR);
  remove("minilzo.lzo");
  f_dst = open("minilzo.lzo", O_RDWR|O_CREAT|O_SYNC, S_IRUSR|S_IWUSR);

  file_size = lseek(f_src, 0, SEEK_END);
  lseek(f_src, 0, SEEK_SET);
  write(f_dst, &file_size, 4);
  while(1)
  {
    src_size = read(f_src, src_buf, BUFFER_SIZE);
    if(src_size)
    {
      write(f_dst, src_buf, src_size);
    }
    else
    {
      break;
    }
  }
  printf("%s size = %d\n", "minilzo.lzo", (int)lseek(f_dst, 0, SEEK_END));
  close(f_src);
  close(f_dst);
  return 0;
}

int compress(void *src, int src_size, void *dst, int *dst_size)
{
  int block_size;
  unsigned char cmprsed_buf[COMPRESS_BUFFER_SIZE<<1];
  unsigned char buffer_hdr[4];
  unsigned char *psrc;
  unsigned char *pdst;
  int cmprs_size, total_cmprs_size = 0;
  int i;
  unsigned char wrkmem_buf[LZO1X_1_MEM_COMPRESS];
  lzo_voidp wrkmem = (lzo_voidp)wrkmem_buf;

  psrc = (unsigned char *)src;
  pdst = (unsigned char *)dst;

  for(i = 0; i < src_size; i += COMPRESS_BUFFER_SIZE)
  {
    if((src_size - i) < COMPRESS_BUFFER_SIZE)
    {
      block_size = src_size - i;
    }
    else
    {
      block_size = COMPRESS_BUFFER_SIZE;
    }

    if(lzo1x_1_compress(&psrc[i], block_size, cmprsed_buf, (lzo_uint *)&cmprs_size, wrkmem) != LZO_E_OK)
    {
      break;
    }

    buffer_hdr[3] = cmprs_size % (1 << 8);
    buffer_hdr[2] = (cmprs_size % (1 << 16)) / (1 << 8);
    buffer_hdr[1] = (cmprs_size % (1 << 24)) / (1 << 16);
    buffer_hdr[0] = cmprs_size / (1 << 24);

    memcpy(pdst, buffer_hdr, 4);
    pdst += 4;
    memcpy(pdst, cmprsed_buf, cmprs_size);
    pdst += cmprs_size;
    total_cmprs_size += cmprs_size + 4;
  }

  if(dst_size) 
    *dst_size = total_cmprs_size;
  return total_cmprs_size;
}

int decompress(void *src, int src_size, void *dst, int *dst_size)
{
  int block_size;
  unsigned char dcmprsed_buf[DCOMPRESS_BUFFER_SIZE<<1];
  unsigned char buffer_hdr[4];
  unsigned char *psrc;
  unsigned char *pdst;
  int dcmprs_size, total_dcmprs_size = 0;
  int i;

  if(!src) return -1;
  if(!dst) return -1;
  psrc = (unsigned char *)src;
  pdst = (unsigned char *)dst;
  for(i = 0; i < src_size; i += 4 + block_size)
  {
    memcpy(buffer_hdr, &psrc[i], 4);
    block_size = buffer_hdr[0] * (1 << 24) + buffer_hdr[1] * (1 << 16) + buffer_hdr[2] * (1 << 8) + buffer_hdr[3];
    if(lzo1x_decompress(&psrc[i+4], block_size, dcmprsed_buf, (lzo_uint *)&dcmprs_size, NULL) != LZO_E_OK)
    {
      break;
    }
    memcpy(pdst, dcmprsed_buf, dcmprs_size);
    pdst += dcmprs_size;
    total_dcmprs_size += dcmprs_size;
  }
  if(dst_size) 
    *dst_size = total_dcmprs_size;
  return total_dcmprs_size;
}

unsigned int crc32_st(unsigned int *pbuf, unsigned int size)
{
	const unsigned int st_const_value = 0x04c11db7;
	unsigned int	crc_value = 0xffffffff;
	unsigned int	xbit;
	unsigned int 	bits;
	unsigned int	i;

	for (i = 0; i < size; i++)
	{
		xbit = 0x80000000;
		for (bits = 0; bits < 32; bits++)
		{
			if (crc_value & 0x80000000)
			{
				crc_value <<= 1;
				crc_value ^= st_const_value;
			}
			else
			{
				crc_value <<= 1;
			}
			if (pbuf[i] & xbit)
			{
				crc_value ^= st_const_value;
			}
			xbit >>= 1;
		}
	}
	return crc_value;
}
